import spawnAsync from '@expo/spawn-async';
import fs from 'fs/promises';
import path from 'path';

import * as env from './env';

export async function initializeGitRepoAsync(repoRoot: string) {
  try {
    await fs.rm(path.join(repoRoot, '.git'), { recursive: true, force: true });
  } catch {}
  await runGitAsync(['init'], { cwd: repoRoot });
  await generateDefaultGitignoreAsync(repoRoot);
}

export async function addAllToGitIndexAsync(repoRoot: string) {
  await runGitAsync(['add', '-A'], { cwd: repoRoot });
}

export async function commitAsync(repoRoot: string, message: string) {
  await runGitAsync(['commit', '-m', message], {
    cwd: repoRoot,
    env: {
      ...process.env,
      GIT_AUTHOR_NAME: 'expo-cng',
      GIT_COMMITTER_NAME: 'expo-cng',
      GIT_AUTHOR_EMAIL: 'noreply@expo.dev',
      GIT_COMMITTER_EMAIL: 'noreply@expo.dev',
    },
  });
}

export async function diffAsync(
  repoRoot: string,
  outputPatchFilePath: string,
  options: string[]
): Promise<void> {
  await runGitAsync(
    [
      'diff',
      '--no-color',
      '--ignore-space-at-eol',
      '--no-ext-diff',
      '--src-prefix=a/',
      '--dst-prefix=b/',
      ...options,
      '--output',
      outputPatchFilePath,
    ],
    {
      cwd: repoRoot,
    }
  );
}

export async function applyPatchAsync(projectRoot: string, patchFilePath: string) {
  return await runGitAsync(['apply', '--ignore-whitespace', patchFilePath], { cwd: projectRoot });
}

export async function getPatchChangedLinesAsync(patchFilePath: string): Promise<number> {
  const stdout = await runGitAsync(['apply', '--numstat', patchFilePath]);
  const lines = stdout.split(/\r?\n/);
  let changedLines = 0;
  for (const line of lines) {
    if (line === '') {
      continue;
    }
    const [added, deleted] = line.split('\t', 2);
    changedLines += Number(added) + Number(deleted);
  }
  return changedLines;
}

async function runGitAsync(args: string[], options?: spawnAsync.SpawnOptions): Promise<string> {
  try {
    const { stdout, stderr } = await spawnAsync('git', args, options);
    if (env.EXPO_DEBUG) {
      console.log(`Running \`git ${args}\` outputs:\nstdout:\n${stdout}\nstderr:\n${stderr}`);
    }
    return stdout.trim();
  } catch (e: any) {
    if (e.code === 'ENOENT') {
      e.message += `\nGit is required to apply patches. Install Git and try again.`;
    } else if (e.stderr) {
      e.message += `\nstderr:\n${e.stderr}`;
    }
    throw e;
  }
}

async function generateDefaultGitignoreAsync(repoRoot: string) {
  const contents = `\
# These files are generated by pod install and should not be included in patch files.
Podfile.lock
contents.xcworkspacedata
`;
  await fs.writeFile(path.join(repoRoot, '.gitignore'), contents, 'utf8');
}
